using System;
using System.Drawing;
using System.Text;
using System.Reflection;
using System.Collections;
using System.Globalization;

namespace Camalon.ColorPicker 
{

	/// <summary>
	/// Provides color conversion functionality.
	/// </summary>
	internal class ColorConverter {

		/// <summary>
		/// Private constructor to ensure that the compiler does not 
		/// automatically generate a public constructor.
		/// </summary>
		private ColorConverter() {}

		/// <summary>
		/// Converts a Color to RGB.
		/// </summary>
		/// <param name="color">A Color object representing the color that is
		/// to be converted to RGB.</param>
		/// <returns>A RGB equivalent.</returns>
		public static RGB ColorToRgb( Color color ) 
		{
			return new RGB(color.R, color.G, color.B );
		}


		public static RGB CMYKtoRGB( CMYK cmyk )
		{
			double k = (double)cmyk.Key		/ 100.0;
			double c = (double)cmyk.Cyan    / 100.0;
			double m = (double)cmyk.Magenda / 100.0;
			double y = (double)cmyk.Yellow  / 100.0;
 
			//Convert CMYK to CMY first:
			//---------------------------------------------------------------
			//{C',M',Y'}	= { C(1 - K) + K, M(1 - K) + K, Y(1 - K) + K } 
			//---------------------------------------------------------------
			c = c * ( 1- k ) + k;
 			m = m * ( 1- k ) + k;
			y = y * ( 1- k ) + k;
			
			//Convert CMY to RGB
			//-----------------------------------
			//{R,G,B} = {1 - C',1 - M',1 - Y'}
			//------------------------------------
			double r = 1.0 - c;
			double g = 1.0 - m;
			double b = 1.0 - y; 
			r *= 255.0 ;
			g *= 255.0 ;
			b *= 255.0 ;
			
			return new RGB( 
				(int)Math.Round(r),
				(int)Math.Round(g),
				(int)Math.Round(b));
		}

		public static CMYK RGBtoCMYK( RGB rgb)
		{
			/*
			{C',M',Y'} = {1 - R,1 - G,1 - B} 

			if MIN(C',M',Y') = 1 
			{
				{ C,M,Y,K } = {0,0,0,1} 
			}
			else 
			{ 
				K			= MIN(C',M',Y')
				{C,M,Y,K}	= { (C' - K)/(1 - K),(M' - K)/(1 - K),(Y' - K)/(1 - K), K } 
			} 
			*/

			double c = 1 - ( rgb.Red   / 255d ); 	
			double m = 1 - ( rgb.Green / 255d ); 	
			double y = 1 - ( rgb.Blue  / 255d ); 	

			double k = GetMinimumValue( new double[]{ c,m,y } );
			double minE = 1d - double.Epsilon;
			double maxE = 1d + double.Epsilon;
			//if( k == 1d)
			if( k <= minE && k >= maxE  ) 
				return new CMYK(0,0,0,100);
			
			double cc = ( c - k ) / ( 1 - k ); 
			double mm = ( m - k ) / ( 1 - k ); 
			double yy = ( y - k ) / ( 1 - k ); 

			return new CMYK(
				(int)Math.Round(cc*100d),
				(int)Math.Round(mm*100d),
				(int)Math.Round(yy*100d),
				(int)Math.Round(k*100d));
			
		}

		/// <summary>
		/// Converts a Color to HSB.
		/// </summary>
		/// <param name="color">A Color object representing the color that is 
		/// to be converted to HSB.</param>
		/// <returns>A HSB equivalent.</returns>
		public static HSB ColorToHsb( Color color ) 
		{
			return RgbToHsb( ColorToRgb( color ) );
		}
		
		/// <summary>
		/// Converts a RGB color structure to a Color object.
		/// </summary>
		/// <param name="rgb">A RGB object representing the color that is to be
		/// converted.</param>
		/// <returns>A Color equivalent.</returns>
		public static Color RgbToColor( RGB rgb ) {
			return Color.FromArgb(rgb.Red, rgb.Green, rgb.Blue );
		}

		/// <summary>
		/// Converts a HSB color structure to a Color object.
		/// </summary>
		/// <param name="hsb">A HSB object representing the color that is to be
		/// converted.</param>
		/// <returns>A color equivalent.</returns>

		public static Color HsbToColor( HSB hsb ) {
			return RgbToColor( HsbToRgb( hsb ) );
		}

		/// <summary>
		/// Converts RGB to HSB.
		/// </summary>
		/// <param name="rgb">A RGB object containing the RGB values that are to 
		/// be converted to HSB values.</param>
		/// <returns>A HSB equivalent.</returns>
		public static HSB RgbToHsb( RGB rgb ) 
		{
			// NOTE #1: Even though we're dealing with a very small range of
			// numbers, the accuracy of all calculations is fairly important.
			// For this reason, I've opted to use double data types instead
			// of float, which gives us a little bit extra precision (recall
			// that precision is the number of significant digits with which
			// the result is expressed).

			double r = rgb.Red / 255d;
			double g = rgb.Green / 255d;
			double b = rgb.Blue / 255d;

			double minValue = GetMinimumValue( r, g, b );
			double maxValue = GetMaximumValue( r, g, b );
			double delta	= maxValue - minValue;

			double hue			= 0;
			double saturation	= 0;
			double brightness	= maxValue * 100;

			if ( maxValue == 0 || delta == 0 ) 
			{

				hue			= 0;
				saturation	= 0;

			} 
			else 
			{

				// NOTE #2: FXCop insists that we avoid testing for floating 
				// point equality (CA1902). Instead, we'll perform a series of
				// tests with the help of Double.Epsilon that will provide 
				// a more accurate equality evaluation.

				if ( minValue == 0 ) 
				{
					saturation = 100;
				} 
				else 
				{
					saturation = ( delta / maxValue ) * 100;
				}

				if ( Math.Abs( r - maxValue ) < Double.Epsilon ) 
				{				
					hue = ( g - b) / delta;
				} 
				else if ( Math.Abs( g - maxValue ) < Double.Epsilon ) 
				{					
					hue = 2 + ( b - r ) / delta;
				} 
				else if ( Math.Abs( b - maxValue ) < Double.Epsilon ) 
				{
					hue = 4 + ( r - g ) / delta;
				}

			}

			hue *= 60;
			if ( hue < 0 ) 
			{
				hue += 360;
			}

			return new HSB( 
				( int ) Math.Round( hue ),
				( int ) Math.Round( saturation ),
				( int ) Math.Round( brightness ) );

		} // RgbToHsb


		/// <summary>
		/// Converts HSB to RGB.
		/// </summary>
		/// <param name="rgb">A HSB object containing the HSB values that are to 
		/// be converted to RGB values.</param>
		/// <returns>A RGB equivalent.</returns>
		public static RGB HsbToRgb( HSB hsb ) 
		{

			
			double h, s, b;
			double red = 0, green = 0, blue = 0;

			int hue = hsb.Hue;
			int n = hue / 360;
			hue -= (n*360);

			h = hue;
			s = ( ( double ) hsb.Saturation ) / 100;
			b = ( ( double ) hsb.Brightness ) / 100;

			if ( s == 0 ) {
				
				red = b;
				green = b;
				blue = b;

			} else {
				
				double p, q, t;

				// the color wheel has six sectors.
				double fractionalSector;
				int sectorNumber;
				double sectorPosition;

				sectorPosition = h / 60;
				sectorNumber = ( int ) Math.Floor( sectorPosition );
				fractionalSector = sectorPosition - sectorNumber;

				p = b * ( 1 - s );
				q = b * ( 1 - ( s * fractionalSector ) );
				t = b * ( 1 - ( s * ( 1 - fractionalSector ) ) );

				// Assign the fractional colors to r, g, and b
				// based on the sector the angle is in.
				switch (sectorNumber) {
					case 0:
						red = b;
						green = t;
						blue = p;
						break;

					case 1:
						red = q;
						green = b;
						blue = p;
						break;

					case 2:
						red = p;
						green = b;
						blue = t;
						break;

					case 3:
						red = p;
						green = q;
						blue = b;
						break;

					case 4:
						red = t;
						green = p;
						blue = b;
						break;

					case 5:
						red = b;
						green = p;
						blue = q;
						break;
				}

			}

			int nRed, nGreen, nBlue;
			nRed = ( int ) Math.Round( red * 255 );
			nGreen = ( int ) Math.Round( green * 255 );
			nBlue = ( int ) Math.Round( blue * 255 );

			return new RGB(nRed, nGreen, nBlue );

		} // HsbToRgb


		/// <summary>
		/// Determines the maximum value of all of the numbers provided in the
		/// variable argument list.
		/// </summary>
		/// <param name="values">An array of doubles.</param>
		/// <returns>The maximum value.</returns>
		private static double GetMaximumValue( params double[] values ) {

			double maxValue = values[0];

			if ( values.Length >= 2 ) {

				double num;

				for ( int i=1; i < values.Length; i++ ) {
			
					num = values[i];
					maxValue = Math.Max( maxValue, num );

				}

			}

			return maxValue;

		} // GetMaximumValue


		/// <summary>
		/// Determines the minimum value of all of the numbers provided in the
		/// variable argument list.
		/// </summary>
		/// <param name="values">An array of doubles.</param>
		/// <returns>The minimum value.</returns>
		
		private static double GetMinimumValue( params double[] values ) {

			double minValue = values[0];

			if ( values.Length >= 2 ) {

				double num;

				for ( int i=1; i < values.Length; i++ ) {
			
					num = values[i];
					minValue = Math.Min( minValue, num );

				}

			}

			return minValue;

		} // GetMinimumValue


		/// <summary>
		/// Creates a string representation from the ARGB color components
		/// </summary>
		/// <param name="rgb">Color</param>
		/// <returns>Hexadecimal string respresentation of ARGB color</returns>
		public static string ARGBToHexString(Color argb)
		{
			return String.Format(null, "{0:x2}{1:x2}{2:x2}{3:x2}", argb.A, argb.R, argb.G, argb.B ).ToUpper(CultureInfo.CurrentCulture);
		}


		public static long ColorToHexValue(Color color)
		{
			string hexString = ARGBToHexString( color ); 
			return Convert.ToInt64( hexString, 16 );
		}



		/// <summary>
		/// The class _ColorSorter orders the colors based on the hue, saturation and brightness. This is the
		/// order that is also used by visual studio.
		/// </summary>
		private class ColorSorter: System.Collections.IComparer 
		{

			#region IComparer Members

			public int Compare (object x, object y) 
			{
				// local variables
				Color					cx, cy;
				float					hx, hy, sx, sy, bx, by;

				// get Color values
				cx = (Color) x;
				cy = (Color) y;
				// get saturation values
				sx = cx.GetSaturation ();
				sy = cy.GetSaturation ();
				// get hue values
				hx = cx.GetHue ();
				hy = cy.GetHue ();
				// get brightness values
				bx = cx.GetBrightness ();
				by = cy.GetBrightness ();

				// determine order
				// 1 : hue       
				if (hx < hy) return -1; 
				else if (hx > hy) return 1;
				else 
				{
					// 2 : saturation
					if (sx < sy) return -1;
					else if (sx > sy) return 1;
					else 
					{
						// 3 : brightness
						if (bx < by) return -1;
						else if (bx > by) return 1;
						else return 0;
					}
				}
			}

			#endregion

		}

		private static ArrayList	_webColors		= GetColorList( true );
		private static ArrayList	_systemColors	= GetColorList( false);

		private static ArrayList GetColorList( bool web )
		{
			PropertyInfo[]	colorProp ;

			if( web)
				colorProp = typeof (Color).GetProperties (BindingFlags.Public | BindingFlags.Static);
			else
				colorProp = typeof (SystemColors).GetProperties (BindingFlags.Public | BindingFlags.Static);
	
			ArrayList colors = new ArrayList( );

			foreach (PropertyInfo prop in colorProp) 
			{
				Color color = (Color) prop.GetValue (null, null);
				colors.Add( color );
			}

			if( web) colors.Sort (new ColorSorter ());
			return colors;
		}

		
		private static string[] GetColorNames( bool web )
		{
			ArrayList list ;

			list = web ? _webColors : _systemColors;
			if( list == null) return new string[]{};
			
			string[] colors = new string[list.Count];

			int i= 0;
			foreach( Color c in list) colors[i++] = c.Name;

			return colors;
		}


		public static string[] SystemColorNames
		{
			get	{	return GetColorNames(false);}
		}

		public static string[] WebColorNames
		{
			get	{	return GetColorNames(true);	}
		}

		public static string UNKNOWN_COLOR_TEXT = "New Color";

		protected internal static string GetColorName( int r, int g, int b )
		{
			foreach( Color c in _webColors)
				if( c.R == r && c.G== g && c.B== b) return c.Name;

			foreach( Color c in _systemColors)
				if( c.R == r && c.G== g && c.B== b) return c.Name;

			return UNKNOWN_COLOR_TEXT;
		}

		protected internal static bool IsTransparent( Color color )
		{
			return color.A == 0 && color.R==255 && color.G == 255 && color.B == 255;
		}

		protected internal static bool IsEmpty( Color color )
		{
			return color.A == 0 && color.R==0 && color.G == 0 && color.B == 0;
		}


		protected internal static string GetColorName( int r, int g, int b, int a )
		{
			if( a == 0 && r==255 && g == 255 && b == 255) return "Transparent";
			if( a == 0 && r==0 && g == 0 && b == 0) return "Empty";
 
			string colorName = GetColorName( r, g, b );

			if( colorName == UNKNOWN_COLOR_TEXT ) return UNKNOWN_COLOR_TEXT;

			if( a == 255 ) return colorName;

			string percentChar = CultureInfo.CurrentCulture.NumberFormat.PercentSymbol;
			int tx = (int)System.Math.Round( (1.0f-((float)a/255.0f))*100f);
			return string.Format(null,"{0}; {1}{2}",colorName, tx, percentChar);

		}

		public static string ToColorString( Color color )
		{
			if( IsEmpty( color )) return "Empty";

			string percentChar = CultureInfo.CurrentCulture.NumberFormat.PercentSymbol;

			int tx = (int)System.Math.Round( (1.0f-((float)color.A/255.0f))*100f);

			if( color.IsKnownColor ) return string.Format(null,"{0}; {1}{2}",color.Name, tx, percentChar);

			string name = GetColorName( color.R,color.G,color.B,color.A );

			if( name == UNKNOWN_COLOR_TEXT) 
				return string.Format(null,"{0}; {1}; {2}; {3}{4}",color.R, color.G, color.B,tx, percentChar);
			else//return string.Format(null,"{0}; {1}{2}",name,tx, percentChar);
				return name;

        }

        public static Color FromColorString(string colorString)
        {
            CultureInfo ci = CultureInfo.CurrentCulture;

            if (colorString.Length == 0 || string.Compare(colorString, "empty", true, ci) == 0)
                return Color.Empty;

            string[] args = colorString.Split(new char[] { ';' });

            Color c = Color.FromName(args[0]);

            char[] percentChar = ci.NumberFormat.PercentSymbol.ToCharArray();


            int a = 255;

            if (!c.IsKnownColor)
            {

                int r = int.Parse(args[0].Trim());
                int g = int.Parse(args[1].Trim());
                int b = int.Parse(args[2].Trim());

                if (args.Length >= 4)
                {
                    string stx = args[3].Trim();
                    string[] _stx = stx.Split(percentChar);
                    stx = _stx[0];

                    float tx = float.Parse(stx);
                    if (tx > 100f) tx = 100f;
                    if (tx < 0f) tx = 0f;
                    tx /= 100f;
                    a = (int)System.Math.Round(255.0f - (tx * 255.0f));
                }

                string name = GetColorName(r, g, b, c.A);

                if (name == UNKNOWN_COLOR_TEXT) return Color.FromArgb(a, r, g, b);
                else return Color.FromName(name);
            }
            else
            {
                if (args.Length >= 2)
                {
                    string stx = args[1].Trim();
                    string[] _stx = stx.Split(percentChar);
                    stx = _stx[0];

                    float tx = float.Parse(stx);
                    if (tx > 100f) tx = 100f;
                    if (tx < 0f) tx = 0f;
                    tx /= 100f;
                    a = (int)System.Math.Round(255.0f - (tx * 255.0f));
                }

                if (a == 255) return c;
                else return Color.FromArgb(a, c);
            }


        }




    } 








} 
